﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

using System.Diagnostics.Contracts;
using HeapModel.ProgInfo;

namespace HeapModel.ConcreteMemory
{
	/// <summary>
	/// This class represents a single atomic chunk of memory. It can be either an object, a struct, (either of these may be on the stack on on the heap), 
	/// a local variable or static home location (or in the case of C# a value on the eval stack).
	/// </summary>
	public sealed class ConcreteNode : IComparable<ConcreteNode>
	{
		/// <summary>
		/// A fixed unique identifier for this memory block.
		/// </summary>
		private readonly int m_objid;

		/// <summary>
		/// The type that this memory block contains.
		/// </summary>
		private readonly TypeIdentifier m_objtype;

		/// <summary>
		/// An array of all the pointer target informtion for the pointers stored in this node.
		/// </summary>
		private ConcretePointer[] m_pts;
		
		/// <summary>
		/// If this is a container, then this is the number of elements in it, otherwise this value is undefined.
		/// </summary>
		private readonly int m_collsize;
		
		/// <summary>
		/// The number of bytes of memory consumed by data entries in this node.
		/// </summary>
		private readonly int m_datasizeinbytes;

		/// <summary>
		/// The number of bytes of memory consumed by implementation overhead in this node.
		/// </summary>
		private readonly int m_gluesizeinbytes;

		[ContractInvariantMethod]
		private void ConcreteObjectInvariant()
		{
			Contract.Invariant(this.m_objid >= 0);
			Contract.Invariant(this.m_objtype != null);
			Contract.Invariant(this.m_datasizeinbytes >= 0);
			Contract.Invariant(this.m_gluesizeinbytes >= 0);
		}

		/// <summary>
		/// Initialize the object.
		/// </summary>
		/// <param name="id">A unique but arbitrary id.</param>
		/// <param name="otype">The concrete type of the object/struct.</param>
		/// <param name="datasizeinbytes">The size, in bytes, used by the data entries in the object.</param>
		/// <param name="gluesizeinbytes">The size, in bytes, used by implementation glue in the object.</param>
		public ConcreteNode(int id, TypeIdentifier otype, int datasizeinbytes, int gluesizeinbytes)
		{
			Contract.Requires(id >= 0);
			Contract.Requires(otype != null);
			Contract.Requires(datasizeinbytes >= 0);
			Contract.Requires(gluesizeinbytes >= 0);

			this.m_objid = id;
			this.m_objtype = otype;
			this.m_collsize = -1;
			this.m_pts = null;
			this.m_datasizeinbytes = datasizeinbytes;
			this.m_gluesizeinbytes = gluesizeinbytes;
		}

		/// <summary>
		/// Initialize the object.
		/// </summary>
		/// <param name="id">A unique but arbitrary id.</param>
		/// <param name="otype">The concrete type of the object/struct.</param>
		/// <param name="csize">If a container the size of the container, otherwise NOT_CONTAINER.</param>
		/// <param name="datasizeinbytes">The size, in bytes, used by the data entries in the object.</param>
		/// <param name="gluesizeinbytes">The size, in bytes, used by implementation glue in the object.</param>
		public ConcreteNode(int id, TypeIdentifier otype, int csize, int datasizeinbytes, int gluesizeinbytes)
		{
			Contract.Requires(id >= 0);
			Contract.Requires(otype != null);
			Contract.Requires(csize >= 0 & otype.IsCollectionType);
			Contract.Requires(datasizeinbytes >= 0);
			Contract.Requires(gluesizeinbytes >= 0);

			this.m_objid = id;
			this.m_objtype = otype;
			this.m_pts = null;
			this.m_collsize = csize;
			this.m_datasizeinbytes = datasizeinbytes;
			this.m_gluesizeinbytes = gluesizeinbytes;
		}

		/// <summary>
		/// After loading all of the fields into cftel, finalize them and add them to the object.
		/// </summary>
		public void FinalizeTargetSet(List<ConcretePointer> cftel)
		{
			Contract.Requires(cftel != null);

			this.m_pts = cftel.ToArray(); 
		}

		/// <summary>
		/// Serialize the object, ignoring the ref component.
		/// </summary>
		public void SerializeConcNodeWithOutRefs(XmlWriter xmlw)
		{
			Contract.Requires(xmlw != null);

			xmlw.WriteStartElement("Node");
			xmlw.WriteAttributeString("ID", this.m_objid.ToString());
			xmlw.WriteAttributeString("Type", this.m_objtype.NameValue);
			xmlw.WriteAttributeString("CSize", this.m_collsize.ToString());
			xmlw.WriteAttributeString("DBytes", this.m_datasizeinbytes.ToString());
			xmlw.WriteAttributeString("GBytes", this.m_gluesizeinbytes.ToString());
			xmlw.WriteEndElement();
		}

		/// <summary>
		/// Serialize the references for the object.
		/// </summary>
		public void SerializeNodePtrs(XmlWriter xmlw)
		{
			Contract.Requires(xmlw != null);

			xmlw.WriteStartElement("ORefs");
			xmlw.WriteAttributeString("ID", this.m_objid.ToString());
			for(int i = 0; i < this.m_pts.Length; ++i)
				this.m_pts[i].SerializePointerEntry(xmlw);
			xmlw.WriteEndElement();
		}

		/// <summary>
		/// Desierialize the node, ignore the references.
		/// </summary>
		public static ConcreteNode ReadConcreteNode(XmlReader rr, Dictionary<String, TypeIdentifier> typedict)
		{
			Contract.Requires(rr != null);
			Contract.Requires(typedict != null);

			String idstr = rr.GetAttribute("ID");
			int idval = idstr.StartsWith("0x") ? Int32.Parse(idstr.Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier) : Int32.Parse(idstr);
			TypeIdentifier tty = typedict[rr.GetAttribute("Type")];
			int csize = (rr.GetAttribute("CSize") != null) ? Int32.Parse(rr.GetAttribute("CSize")) : -1;
			int dsize = (rr.GetAttribute("DBytes") != null) ? Int32.Parse(rr.GetAttribute("DBytes")) : 0;
			int gsize = (rr.GetAttribute("GBytes") != null) ? Int32.Parse(rr.GetAttribute("GBytes")) : 0;
			rr.ReadStartElement("Node");

			if(tty.IsCollectionType)
				return new ConcreteNode(idval, tty, csize, dsize, gsize);
			else
				return new ConcreteNode(idval, tty, dsize, gsize);
		}

		/// <summary>
		/// Deserialize the references into the concrete node.
		/// </summary>
		public void ReadConcreteNodeRefsInto(XmlReader rr, Dictionary<String, AccessPath> accpdict, Dictionary<String, OffsetIdentifier> offsetdict, Dictionary<int, ConcreteNode> nodedict, ref int sorttokenctr)
		{
			Contract.Requires(rr != null);
			Contract.Requires(accpdict != null);
			Contract.Requires(nodedict != null);

			List<ConcretePointer> ptl = new List<ConcretePointer>();
			if(rr.IsEmptyElement)
				rr.ReadStartElement("ORefs");
			else
			{
				rr.ReadStartElement("ORefs");
				while(rr.IsStartElement("Ptr"))
				{
					ConcretePointer cp = ConcretePointer.ReadPointerEntry(rr, accpdict, offsetdict, nodedict, ref sorttokenctr);
					ptl.Add(cp);
				}
				rr.ReadEndElement();
			}

			this.FinalizeTargetSet(ptl);
		}

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder(this.m_objid.ToString() + "@(" + this.m_objtype.NameValue);

			if(this.m_objtype.IsCollectionType)
				sb.Append("|" + this.m_collsize + "|");

			if(this.m_pts.Length > 0)
			{
				sb.Append("{");
				for(int i = 0; i < this.m_pts.Length; ++i)
				{
					if(i != 0)
						sb.Append(", ");
					sb.Append(this.m_pts[i].ToString());
				}
				sb.Append("}");
			}

			sb.Append(")");
			return sb.ToString();
		}


		public int CompareTo(ConcreteNode other)
		{ return this.m_objid - other.m_objid; }

		/// <summary>
		/// The unique id associated with this object.
		/// </summary>
		public int ConcreteNodeID { get { return this.m_objid; } }

		/// <summary>
		/// The type of this object.
		/// </summary>
		public TypeIdentifier ConcreteNodeType 
		{ 
			get 
			{
				Contract.Ensures(Contract.Result<TypeIdentifier>() != null);
				return this.m_objtype; 
			} 
		}

		/// <summary>
		/// Array of all the pointer targets in this object.
		/// </summary>
        public ConcretePointer[] PointerTargets
        {
            get
            {
                Contract.Ensures(Contract.Result<ConcretePointer[]>() != null);

                return this.m_pts;
            }
        }

		/// <summary>
		/// The size of this container object.
		/// </summary>
		public int ContainerSizeInfo 
		{ 
			get 
			{
				Contract.Requires(this.ConcreteNodeType.IsCollectionType);

				return this.m_collsize; 
			} 
		}

		/// <summary>
		/// The number of bytes consumed by data in this object.
		/// </summary>
		public int DataSizeInBytes 
		{ get { return this.m_datasizeinbytes; } }

		/// <summary>
		/// The number of bytes consumed by inplementation overhead in this object.
		/// </summary>
		public int GlueSizeInBytes
		{ get { return this.m_gluesizeinbytes; } }
	}

	/// <summary>
	/// How we represent the relation between an access path in this <c>ConcreteNode</c> and the pointer/reference target in another 
	/// <c>ConcreteNode</c>. 
	/// MAY HAVE MULITPLE OF THE SAME NAMES IN COLLECTION ENTIRES!!!!
	/// </summary>
	public struct ConcretePointer : IEquatable<ConcretePointer>, IComparable<ConcretePointer>
	{
		/// <summary>
		/// The path to the location that the pointer is stored at in this <c>ConcreteNode</c>.
		/// </summary>
		private readonly AccessPath m_locinsource;

		/// <summary>
		/// The target object associated with this entry, null if the underlying target is null.
		/// </summary>
		private readonly ConcreteNode m_trgt;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_locinsource != null);
			Contract.Invariant(this.m_trgt != null);
		}

		/// <summary>
		/// Construct an immutable struct that represents a single pointer value in an object.
		/// </summary>
		public ConcretePointer(AccessPath locinsource, ConcreteNode target)
		{
			Contract.Requires(locinsource != null);
			Contract.Requires(target != null);

			this.m_locinsource = locinsource;
			this.m_trgt = target;
		}

		public bool Equals(ConcretePointer other)
		{ return (this.m_locinsource == other.m_locinsource) & (this.m_trgt == other.Target); }

		public override bool Equals(object obj)
		{ return (obj is ConcretePointer) && this.Equals((ConcretePointer)obj); }

		public override int GetHashCode()
		{ return (this.m_locinsource.GetHashCode()) ^ (this.m_trgt.ConcreteNodeID); }

		public int CompareTo(ConcretePointer other)
		{
			if(this.m_locinsource != other.m_locinsource)
				return this.m_locinsource.CompareTo(other.m_locinsource);
			else
				return this.m_trgt.ConcreteNodeID - other.m_trgt.ConcreteNodeID;
		}

		public static bool operator ==(ConcretePointer lhs, ConcretePointer rhs)
		{ return lhs.Equals(rhs); }

		public static bool operator !=(ConcretePointer lhs, ConcretePointer rhs)
		{ return !(lhs.Equals(rhs)); }

		public static bool operator <(ConcretePointer lhs, ConcretePointer rhs)
		{ return lhs.CompareTo(rhs) < 0; }

		public static bool operator >(ConcretePointer lhs, ConcretePointer rhs)
		{ return lhs.CompareTo(rhs) > 0; }

		public override String ToString()
		{ return "[" + this.m_locinsource.ToString() + " -> " + this.m_trgt.ConcreteNodeID.ToString() + "]"; }

		/// <summary>
		/// Write out the info for serialization to an xml file.
		/// </summary>
		public void SerializePointerEntry(XmlWriter xmlw)
		{
			Contract.Requires(xmlw != null);

			xmlw.WriteStartElement("Ptr");
			xmlw.WriteAttributeString("Target", this.m_trgt.ConcreteNodeID.ToString());
			this.m_locinsource.XMLify(xmlw);
			xmlw.WriteEndElement();
		}

		/// <summary>
		/// Return the deserialized value.
		/// </summary>
		public static ConcretePointer ReadPointerEntry(XmlReader xmlr, Dictionary<String, AccessPath> accpdict, Dictionary<String, OffsetIdentifier> offsetdict, Dictionary<int, ConcreteNode> nodedict, ref int sorttokenctr)
		{
			Contract.Requires(xmlr != null);
			Contract.Requires(accpdict != null);
			Contract.Requires(nodedict != null);

			String idstr = xmlr.GetAttribute("Target");
			int trgtval = idstr.StartsWith("0x") ? Int32.Parse(idstr.Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier) : Int32.Parse(idstr);
			ConcreteNode node = nodedict[trgtval];
			xmlr.ReadStartElement("Ptr");

			AccessPath sloc = AccessPath.DeXMLify(xmlr, accpdict, offsetdict, ref sorttokenctr);
			
			xmlr.ReadEndElement();

			return new ConcretePointer(sloc, node);
		}

		/// <summary>
		/// The location in the source node associated with this entry.
		/// </summary>
		public AccessPath LocationInSource 
		{ 
			get 
			{
				Contract.Ensures(Contract.Result<AccessPath>() != null);

				return this.m_locinsource; 
			} 
		}

		/// <summary>
		/// The target object associated with this entry.
		/// </summary>
		public ConcreteNode Target 
		{
			get
			{
				Contract.Ensures(Contract.Result<ConcreteNode>() != null);

				return this.m_trgt; 
			}
		}
	}
}
